package com.feng.openFeignPlus.scan;


import com.feng.openFeignPlus.annotion.EnableOpenFeignPlus;
import com.feng.openFeignPlus.annotion.RemoteFeignMethod;
import com.feng.openFeignPlus.annotion.RemoteFeignService;
import com.feng.openFeignPlus.register.HandlerDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.*;

public class Scanner implements ImportBeanDefinitionRegistrar, EnvironmentAware {
    private Environment environment;

    private Set<BeanDefinition> useBeanSet = new HashSet<>();

    public static List<HandlerDefinition> handlerDefinitionList = new ArrayList<>();

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        System.out.println("开始注册Feign接口");
        //聚合扫描的路径
        Set<String> basePackages = getBasePackages(importingClassMetadata);
        //获取扫描器
        ClassPathScanningCandidateComponentProvider scanner = getScanner();
        //扫描并注册bd
        for (String basePackage : basePackages) {
            useBeanSet.addAll(scanner.findCandidateComponents(basePackage));
        }
        toHandlerDefinition();

    }

    private String toLow(String  str) {
        String firstLetter = str.substring(0, 1).toLowerCase();
        String restLetters = str.substring(1);
        String result = firstLetter + restLetters;

        System.out.println(result); // 输出 "helloWorld"
        return result;
    }

    private void toHandlerDefinition() {
        for (BeanDefinition beanDefinition : useBeanSet) {
            try {
                Class<?> target = Class.forName(beanDefinition.getBeanClassName());
                String beanName = getBeanName(beanDefinition);
                //need get all methods of the target, and judge which has the annotation of @RemoteFeignMethod
                for (Method method : target.getMethods()) {
                    if (method.isAnnotationPresent(RemoteFeignMethod.class)) {
                        HandlerDefinition handlerDefinition = new HandlerDefinition();
                        RemoteFeignMethod annotation = method.getAnnotation(RemoteFeignMethod.class);
                        String uri = annotation.name().endsWith("")  ? method.getName() : annotation.name();
                        handlerDefinition.setHandlerName(beanName)
                                .setFactoryClass(target)
                                .setMethod(method)
                                .setPath(beanDefinition.getBeanClassName() + "." + uri)
                                .setParamsNameList(Arrays.asList(new LocalVariableTableParameterNameDiscoverer().getParameterNames(method)))
                                .setParamsTypeList(Arrays.asList(method.getParameterTypes()));
                        this.handlerDefinitionList.add(handlerDefinition);
                    }

                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    }

    private String getBeanName(BeanDefinition beanDefinition) throws ClassNotFoundException {
        String name = toLow(beanDefinition.getBeanClassName().substring(beanDefinition.getBeanClassName().lastIndexOf(".")+1));
        Class<?> target = Class.forName(beanDefinition.getBeanClassName());
        if (Objects.nonNull(target.getAnnotation(Component.class)) && !Objects.equals("",  target.getAnnotation(Component.class).value())) {
            name = target.getAnnotation(Component.class).value();
        } else if (Objects.nonNull(target.getAnnotation(Service.class)) && !Objects.equals("",  target.getAnnotation(Service.class).value())) {
            name = target.getAnnotation(Service.class).value();
        }
        return name;

    }

    private ClassPathScanningCandidateComponentProvider getScanner() {
        //不使用默认的过滤器
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false, environment);
        scanner.addIncludeFilter((metadataReader, metadataReaderFactory) -> {
            AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
            //使用了@RemoteFeignService 并且是实现类
            return metadata.getAnnotationTypes().contains(RemoteFeignService.class.getTypeName())
                    && !metadataReader.getClassMetadata().isInterface();
        });

        return scanner;
    }

    /**
     * 获取扫描组件的包路径
     */
    protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
        Map<String, Object> attributes = importingClassMetadata
                .getAnnotationAttributes(EnableOpenFeignPlus.class.getCanonicalName());

        Set<String> basePackages = new HashSet<>();

        for (String pkg : (String[]) attributes.get("basePackages")) {
            if (StringUtils.hasText(pkg)) {
                basePackages.add(pkg);
            }
        }
        for (Class<?> clazz : (Class<?>[]) attributes.get("basePackageClasses")) {
            basePackages.add(ClassUtils.getPackageName(clazz));
        }

        if (basePackages.isEmpty()) {
            basePackages.add(
                    ClassUtils.getPackageName(importingClassMetadata.getClassName()));
        }
        return basePackages;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    public Set<BeanDefinition> getUseBeanSet() {
        return useBeanSet;
    }

    public List<HandlerDefinition> getHandlerDefinitionList() {
        return this.handlerDefinitionList;
    }

}
